﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using Pfz.WpfControls;

namespace Pfz.Extensions
{
	/// <summary>
	/// Adds methods to find enumerate all controls and sub-controls.
	/// </summary>
	public static class PfzElementExtensions
	{
		/// <summary>
		/// Gets the actual element and all it's sub-elements recursivelly.
		/// </summary>
		public static IEnumerable<UIElement> EnumerateElementsRecursive(this UIElement element)
		{
			if (element == null)
				yield break;
			
			yield return element;

			var contentCreatorControl = element as ContentCreatorControl;
			if (contentCreatorControl != null)
				contentCreatorControl.CreateContentNowIfNeeded();
			
			var contentControl = element as ContentControl;
			if (contentControl != null)
			{
				var content = contentControl.Content as UIElement;
				foreach(var subControl in content.EnumerateElementsRecursive())
					yield return subControl;
					
				yield break;
			}
			
			Decorator decorator = element as Decorator;
			if (decorator != null)
			{
				var content = decorator.Child;
				foreach(var subControl in content.EnumerateElementsRecursive())
					yield return subControl;
				
				yield break;
			}
			
			Panel panel = element as Panel;
			if (panel != null)
			{
				foreach(var child in panel.Children)
				{
					var content = child as UIElement;
					foreach(var item in content.EnumerateElementsRecursive())
						yield return item;
				}
				
				yield break;
			}
			
			ItemsControl itemsControl = element as ItemsControl;
			if (itemsControl != null)
			{
				foreach(var child in itemsControl.Items)
				{
					var content = child as UIElement;
					foreach(var item in content.EnumerateElementsRecursive())
						yield return item;
				}
				
				yield break;
			}
		}
		
		/// <summary>
		/// Gets all elements of the given type, recursivelly, from the given base element.
		/// </summary>
		public static IEnumerable<T> EnumerateElementsRecursive<T>(this UIElement baseElement)
		where
			T: class
		{
			foreach(var element in baseElement.EnumerateElementsRecursive())
			{
				var typedElement = element as T;
				if (typedElement != null)
					yield return typedElement;
			}
		}
		
		/// <summary>
		/// Gets the first parent found of the given type.
		/// </summary>
		public static T GetParentOfType<T>(this FrameworkElement element)
		where
			T: class
		{
			while(element != null)
			{
				var parent = element.Parent;
				if (parent == null)
					return null;
				
				T result = parent as T;
				if (result != null)
					return result;
				
				element = parent as FrameworkElement;
			}
			
			return null;
		}
		
		/// <summary>
		/// Gets the first element of the given type in the actual element.
		/// </summary>
		public static T FirstOrDefault<T>(this UIElement element)
		where
			T: class
		{
			foreach(var subElement in element.EnumerateElementsRecursive<T>())
				return subElement;
			
			return default(T);
		}

		/// <summary>
		/// Finds the control that sets the given "cascading" property, considering the given element and its parent.
		/// </summary>
		public static DependencyObject FindPropertySetter(this FrameworkElement element, DependencyProperty property)
		{
			if (property == null)
				throw new ArgumentNullException("property");

			if (element == null)
				return null;

			if (element.ReadLocalValue(property) != DependencyProperty.UnsetValue)
				return element;

			while(true)
			{
				var result = element.Parent;
				if (result == null)
					return result;

				if (result.ReadLocalValue(property) != DependencyProperty.UnsetValue)
					return result;

				element = result as FrameworkElement;
				if (element == null)
					return null;
			}
		}

		/// <summary>
		/// This method sets the value of a property, considering it is already set, be in this element or
		/// in one of its parents.
		/// Throws an exception if the property is not set until the last parent.
		/// </summary>
		public static void ChangeValueWhereSet(this FrameworkElement element, DependencyProperty property, object value)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			if (property == null)
				throw new ArgumentNullException("property");

			DependencyObject origin = FindPropertySetter(element, property);
			if (origin == null)
				throw new ArgumentException("property is not set.");

			origin.SetValue(property, value);
		}

		/// <summary>
		/// Tries to focus all elements and sub-elements of this control.
		/// Returns when the first one is focused.
		/// </summary>
		public static bool FocusFirstFocusable(this FrameworkElement element)
		{
			foreach(var control in element.EnumerateElementsRecursive<UIElement>())
				if (control.Focus())
					return true;

			return false;
		}
	}
}
